In [1]:
import pandas as pd
from pandas import json_normalize
import datetime
import numpy as np
import json
import requests
import urllib.request
from datetime import datetime
import matplotlib.pyplot as plt
from scipy import interpolate
import statsmodels.api as sm
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.ticker import FuncFormatter
In [2]:
url = "https://deribit.com/api/v2/public/get_instruments?currency=BTC&kind=option&expired=false"
with urllib.request.urlopen(url) as url:
get_inst_names = json.loads(url.read().decode())
inst_names = pd.DataFrame(get_inst_names['result'])#.set_index('instrument_name')
inst_names['creation_date'] = pd.to_datetime(inst_names['creation_timestamp'], unit='ms')
inst_names['expiration_date'] = pd.to_datetime(inst_names['expiration_timestamp'], unit='ms')
api_data_list = []
for x in inst_names['instrument_name']:
url = f'https://www.deribit.com/api/v2/public/get_order_book?instrument_name={x}&kind=option&expired=false'
with urllib.request.urlopen(url) as response:
api_data = json.loads(response.read().decode())
api_data_df = pd.DataFrame([api_data])
api_data_list.append(api_data_df)
all_data = pd.concat(api_data_list, ignore_index=True)
all_data = pd.json_normalize(all_data['result'])
In [4]:
data = all_data
exclude_columns = ['instrument_name', 'underlying_index']
data = data.apply(lambda x: pd.to_numeric(x, errors='ignore') if x.name not in exclude_columns else x)
strike = []
for index, i in enumerate(data['instrument_name']):
i = float(i.split('-')[2])
strike.append(i)
data["strikePx"] = strike
maturity = []
for index, i in enumerate(data['instrument_name']):
i = i.split('-')[1]
i = pd.to_datetime(i, unit ='ns')
maturity.append(i)
data["maturity"] = maturity
days_to_maturity = []
for index, i in enumerate(data['maturity']):
i = (i - datetime.today()).days
days_to_maturity.append(i)
data["days_to_maturity"] = days_to_maturity
# Moneyness: 'underlying price' is the price of underlying Futures
data['moneyness'] = data['strikePx'].astype(float)/ data['underlying_price'].astype(float)
#log Moneyness, making extreme values less extreme
#data['moneyness'] = np.log(data['strikePx'].astype(float)/ data['fwdPx'].astype(float))
data.loc[data['instrument_name'].str.contains('-P'), 'moneyness'] = data.loc[data['instrument_name'].str.contains('-P'), 'moneyness'] * -1
data = data.sort_values(['days_to_maturity','strikePx']).query('days_to_maturity > 0')
all_calls = data[data['instrument_name'].str.contains('-C')].sort_values(['days_to_maturity', 'strikePx']).query('days_to_maturity > 0')
all_puts = data[data['instrument_name'].str.contains('-P')].sort_values(['days_to_maturity', 'strikePx']).query('days_to_maturity > 0')
In [5]:
#%matplotlib notebook
# Interpolate implied volatility using a cubic spline
'''
Cubic Spline interpolation uses cubic polynominals to interpolate between data points
'''
def plot_iv_surf(x,y,z,x2=None,y2=None,z2=None,label=''):
fig = plt.figure(3, figsize=(6,6))
ax=plt.axes(projection='3d')
ax.set_title('BTC Implied Volatility Surface, OKX')
ax.set_zlabel('Implied Volatility')
plt.xlabel('Strike / FuturesPx')
plt.ylabel('Days To Maturity')
#ax.zaxis.set_major_formatter(FuncFormatter(lambda z, _: '{:.0%}'.format(z)))
if z2 is not None:
ax.scatter3D(x2,y2,z2, c='r', s=100,label=label)
ax.plot_surface(x, y, z, rstride=1, cstride=1,alpha=0.5)
ax.legend()
x = data['moneyness']
y = data['days_to_maturity']
z = data['mark_iv']/100
X,Y = np.meshgrid(np.linspace(.95,1.05,99),np.linspace(1,np.max(y),100))
Z = interpolate.griddata(np.array([x,y]).T,np.array(z),(X,Y), method='cubic')
xyz = pd.DataFrame({'x':x,'y':y,'z':z})
xyz = xyz.query('x>0.95 & x<1.05')
plot_iv_surf(X,Y,Z,xyz['x'],xyz['y'],xyz['z'],'Observed IV')
iv_df = pd.DataFrame(Z, index=np.linspace(10,np.max(y),100), columns=np.linspace(.95,1.05,99))
In [6]:
import plotly.graph_objects as go
def plot_iv_surf_plotly(x, y, z, x2=None, y2=None, z2=None, label=''):
fig = go.Figure()
# Create the surface plot
fig.add_trace(go.Surface(x=x, y=y, z=z, colorscale='Viridis', opacity=0.7))
# Add scatter points if provided
if x2 is not None and y2 is not None and z2 is not None:
fig.add_trace(go.Scatter3d(
x=x2,
y=y2,
z=z2,
mode='markers',
marker=dict(size=5, color='red', symbol='circle'),
name=label
))
fig.update_layout(
title='BTC Implied Volatility Surface, OKX',
scene=dict(
xaxis_title='Strike / FuturesPx',
yaxis_title='Days To Maturity',
zaxis_title='Implied Volatility',
zaxis=dict(tickformat=".0%")
),
legend=dict(x=0, y=1),
width=1200, # Set the width of the plot
height=800 # Set the height of the plot
)
fig.show()
# Interpolate implied volatility using a cubic spline
x = data['moneyness']
y = data['days_to_maturity']
z = data['mark_iv'] / 100
X, Y = np.meshgrid(np.linspace(.95, 1.05, 99), np.linspace(1, np.max(y), 100))
Z = interpolate.griddata(np.array([x, y]).T, np.array(z), (X, Y), method='cubic')
xyz = pd.DataFrame({'x': x, 'y': y, 'z': z})
xyz = xyz.query('x > 0.95 & x < 1.05')
plot_iv_surf_plotly(X, Y, Z, xyz['x'], xyz['y'], xyz['z'], 'Observed IV')
iv_df = pd.DataFrame(Z, index=np.linspace(10, np.max(y), 100), columns=np.linspace(.95, 1.05, 99))
In [ ]: